<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_20</span></h2>

  <p>框”的布局，我们必须压下一个卡片使不同的对话框带到前面来。在AWT里不是这样的：CardLayout是简单的空的空格，我们可以自由地把新卡片带到前面来。（JFC/Swing库包括卡片式的窗格看起来非常的棒，且可以我们处理所有的细节。）</p>

  <p>1. 联合布局（Combining layouts)</p>

  <p>下面的例子联合了更多的布局类型，在最初只有一个布局管理器被程序片或应用程序操作看起来相当的困难。这是事实，但如果我们创建更多的面板对象，每个面板都能拥有一个布局管理器，并且像被集成到程序片或应用程序中一样使用程序片或应用程序的布局管理器。这就象下面程序中的一样给了我们更多的灵活性：</p>

  <p>//: CardLayout1.java</p>

  <p>// Demonstrating the CardLayout</p>

  <p>import java.awt.*;</p>

  <p>import java.applet.Applet;</p>

  <p>class ButtonPanel extends Panel {</p>

  <p>ButtonPanel(String id) {</p>

  <p>setLayout(new BorderLayout());</p>

  <p>add("Center", new Button(id));</p>

  <p>}</p>

  <p>}</p>

  <p>public class CardLayout1 extends Applet {</p>

  <p>Button</p>

  <p>first = new Button("First"),</p>

  <p>second = new Button("Second"),</p>

  <p>third = new Button("Third");</p>

  <p>Panel cards = new Panel();</p>

  <p>CardLayout cl = new CardLayout();</p>

  <p>public void init() {</p>

  <p>setLayout(new BorderLayout());</p>

  <p>Panel p = new Panel();</p>

  <p>p.setLayout(new FlowLayout());</p>

  <p>p.add(first);</p>

  <p>p.add(second);</p>

  <p>p.add(third);</p>

  <p>add("North", p);</p>

  <p>cards.setLayout(cl);</p>

  <p>cards.add("First card",</p>

  <p>new ButtonPanel("The first one"));</p>

  <p>cards.add("Second card",</p>

  <p>new ButtonPanel("The second one"));</p>

  <p>cards.add("Third card",</p>

  <p>new ButtonPanel("The third one"));</p>

  <p>add("Center", cards);</p>

  <p>}</p>

  <p>public boolean action(Event evt, Object arg) {</p>

  <p>if (evt.target.equals(first)) {</p>

  <p>cl.first(cards);</p>

  <p>}</p>

  <p>else if (evt.target.equals(second)) {</p>

  <p>cl.first(cards);</p>

  <p>cl.next(cards);</p>

  <p>}</p>

  <p>else if (evt.target.equals(third)) {</p>

  <p>cl.last(cards);</p>

  <p>}</p>

  <p>else</p>

  <p>return super.action(evt, arg);</p>

  <p>return true;</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这个例子首先会创建一种新类型的面板：BottonPanel（按钮面板）。它包括一个单独的按钮，安放在BorderLayout的中央，那意味着它将充满整个的面板。按钮上的标签将让我们知道我们在CardLayout上的那个面板上。</p>

  <p>在程序片里，面板卡片上将存放卡片和布局管理器CL因为CardLayout必须组成类，因为当我们需要处理卡片时我们需要访问这些句柄。</p>

  <p>这个程序片变成使用BorderLayout来取代它的默认FlowLayout，创建面板来容纳三个按钮（使用FlowLayout），并且这个面板安置在程序片末尾的“North”。卡片面板增加到程序片的“Center”里，有效地占据面板的其余地方。</p>

  <p>当我们增加BottonPanels(或者任何其它我们想要的组件）到卡片面板时，add()方法的第一个自变量不是“North”，“South”等等。相反的是，它是一个描述卡片的字符串。如果我们想轻击那张卡片使用字符串，我们就可以使用，虽然这字符串不会显示在卡片的任何地方。使用的方法不是使用action()；代之使用first()、next()和last()等方法。请查看我们有关其它方法的文件。</p>

  <p>在Java中，使用的一些卡片式面板结构十分的重要，因为（我们将在后面看到）在程序片编程中使用的弹出式对话框是十分令人沮丧的。对于Java 1.0版的程序片而言，CardLayout是唯一有效的取得很多不同的“弹出式”的窗体。</p>

  <p>13.12.5 GridBagLayout</p>

  <p>很早以前，人们相信所有的恒星、行星、太阳及月亮都围绕地球公转。这是直观的观察。但后来天文学家变得更加的精明，他们开始跟踪个别星体的移动，它们中的一些似乎有时在轨道上缓慢运行。因为天文学家知道所有的天体都围绕地球公转，天文学家花费了大量的时间来讨论相关的方程式和理论去解释天体对象的运行。当我们试图用GridBagLayout来工作时，我们可以想像自己为一个早期的天文学家。基础的条例是（公告：有趣的是设计者居然在太阳上(这可能是在天体图中标错了位置所致，译者注)）所有的天体都将遵守规则来运行。哥白尼日新说（又一次不顾嘲讽，发现太阳系内的所有的行星围绕太阳公转。）是使用网络图来判断布局，这种方法使得程序员的工作变得简单。直到这些增加到Java里，我们忍耐（持续的冷嘲热讽）西班牙的GridBagLayout和GridBagConstraints狂热宗教。我们建议废止GridBagLayout。取代它的是，使用其它的布局管理器和特殊的在单个程序里联合几个面板使用不同的布局管理器的技术。我们的程序片看起来不会有什么不同；至少不足以调整GridBagLayout限制的麻烦。对我而言，通过一个例子来讨论它实在是令人头痛（并且我不鼓励这种库设计）。相反，我建议您从阅读Cornell和Horstmann撰写的《核心Java》（第二版，Prentice-Hall出版社，1997年）开始。</p>

  <p>在这范围内还有其它的：在JFC/Swing库里有一个新的使用Smalltalk的受人欢迎的“Spring and Struts”布局管理器并且它能显著地减少GridBagLayout的需要。</p>

  <p>13.13 action的替代品</p>

  <p>正如早先指出的那样，action()并不是我们对所有事进行分类后自动为handleEvent()调用的唯一方法。有三个其它的被调用的方法集，如果我们想捕捉某些类型的事件（键盘、鼠标和焦点事件），因此我们不得不过载规定的方法。这些方法是定义在基础类组件里，所以他们几乎在所有我们可能安放在窗体中的组件中都是有用的。然而，我们也注意到这种方法在Java 1.1版中是不被支持的，同样尽管我们可能注意到继承代码利用了这种方法，我们将会使用Java 1.1版的方法来代替（本章后面有详细介绍）。</p>

  <p>组件方法 何时调用</p>

  <p>action(Event evt, Object what) 当典型的事件针对组件发生（例如，当按下一个按钮或下拉列表项目被选中）时调用</p>

  <p>keyDown(Event evt, int key） 当按键被按下，组件拥有焦点时调用。第二个自变量是按下的键并且是冗余的是从evt.key处复制来的</p>

  <p>keyup(Event evt, int key) 当按键被释放，组件拥有焦点时调用</p>

  <p>lostFocus(Event evt, Object what) 焦点从目标处移开时调用。通常，what是从evt.arg里冗余复制的</p>

  <p>gotFocus(Event evt, Object what) 焦点移动到目标时调用</p>

  <p>mouseDown(Event evt, int x，int y) 一个鼠标按下存在于组件之上，在X，Y座标处时调用</p>

  <p>mouseUp(Event evt, int x, int y) 一个鼠标升起存在于组件之上时调用</p>

  <p>mouseMove(Event evt, int x, int y) 当鼠标在组件上移动时调用</p>

  <p>mouseDrag(Event evt, int x, int y） 鼠标在一次mouseDown事件发生后拖动。所有拖动事件都会报告给内部发生了mouseDown事件的那个组件，直到遇到一次mouseUp为止</p>

  <p>mouseEnter(Event evt, int x, int y) 鼠标从前不在组件上方，但目前在</p>

  <p>mouseExit(Event evt, int x, int y) 鼠标曾经位于组件上方，但目前不在</p>

  <p>当我们处理特殊情况时――一个鼠标事件，例如，它恰好是我们想得到的鼠标事件存在的座标，我们将看到每个程序接收一个事件连同一些我们所需要的信息。有趣的是，当组件的handleEvent()调用这些方法时（典型的事例），附加的自变量总是多余的因为它们包含在事件对象里。事实上，如果我们观察component.handleEvent()的源代码，我们能发现它显然将增加的自变量抽出事件对象（这可能是考虑到在一些语言中无效率的编码，但请记住Java的焦点是安全的，不必担心。）试验对我们表明这些事件事实上在被调用并且作为一个有趣的尝试是值得创建一个过载每个方法的程序片，（action()的过载在本章的其它地方）当事件发生时显示它们的相关数据。</p>

  <p>这个例子同样向我们展示了怎样制造自己的按钮对象，因为它是作为目标的所有事件权益来使用。我可能会首先（也是必须的）假设制造一个新的按钮，我们从按钮处继承。但它并不能运行。取而代之的是，我们从画布组件处（一个非常普通组件）继承，并在其上不使用paint()方法画出一个按钮。正如我们所看到的，自从一些代码混入到画按钮中去，按钮根本就不运行，这实在是太糟糕了。（如果您不相信我，试图在例子中为画布组件交换按钮，请记住调用称为super的基础类构建器。我们会看到按钮不会被画出，事件也不会被处理。）</p>

  <p>myButton类是明确说明的：它只和一个自动事件（AutoEvent）“父窗口”一起运行（父窗口不是一个基础类，它是按钮创建和存在的窗口。）。通过这个知识，myButton可能进入到父窗口并且处理它的文字字段，必然就能将状态信息写入到父窗口的字段里。当然这是一种非常有限的解决方法，myButton仅能在连结AutoEvent时被使用。这种代码有时称为“高度结合”。但是，制造myButton更需要很多的不是为例子（和可能为我们将写的一些程序片）担保的努力。再者，请注意下面的代码使用了Java 1.1版不支持的API。</p>

  <p>//: AutoEvent.java</p>

  <p>// Alternatives to action()</p>

  <p>import java.awt.*;</p>

  <p>import java.applet.*;</p>

  <p>import java.util.*;</p>

  <p>class MyButton extends Canvas {</p>

  <p>AutoEvent parent;</p>

  <p>Color color;</p>

  <p>String label;</p>

  <p>MyButton(AutoEvent parent,</p>

  <p>Color color, String label) {</p>

  <p>this.label = label;</p>

  <p>this.parent = parent;</p>

  <p>this.color = color;</p>

  <p>}</p>

  <p>public void paint(Graphics g) {</p>

  <p>g.setColor(color);</p>

  <p>int rnd = 30;</p>

  <p>g.fillRoundRect(0, 0, size().width,</p>

  <p>size().height, rnd, rnd);</p>

  <p>g.setColor(Color.black);</p>

  <p>g.drawRoundRect(0, 0, size().width,</p>

  <p>size().height, rnd, rnd);</p>

  <p>FontMetrics fm = g.getFontMetrics();</p>

  <p>int width = fm.stringWidth(label);</p>

  <p>int height = fm.getHeight();</p>

  <p>int ascent = fm.getAscent();</p>

  <p>int leading = fm.getLeading();</p>

  <p>int horizMargin = (size().width - width)/2;</p>

  <p>int verMargin = (size().height - height)/2;</p>

  <p>g.setColor(Color.white);</p>

  <p>g.drawString(label, horizMargin,</p>

  <p>verMargin + ascent + leading);</p>

  <p>}</p>

  <p>public boolean keyDown(Event evt, int key) {</p>

  <p>TextField t =</p>

  <p>(TextField)parent.h.get("keyDown");</p>

  <p>t.setText(evt.toString());</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean keyUp(Event evt, int key) {</p>

  <p>TextField t =</p>

  <p>(TextField)parent.h.get("keyUp");</p>

  <p>t.setText(evt.toString());</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean lostFocus(Event evt, Object w) {</p>

  <p>TextField t =</p>

  <p>(TextField)parent.h.get("lostFocus");</p>

  <p>t.setText(evt.toString());</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean gotFocus(Event evt, Object w) {</p>

  <p>TextField t =</p>

  <p>(TextField)parent.h.get("gotFocus");</p>

  <p>t.setText(evt.toString());</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean</p>

  <p>mouseDown(Event evt,int x,int y) {</p>

  <p>TextField t =</p>

  <p>(TextField)parent.h.get("mouseDown");</p>

  <p>t.setText(evt.toString());</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean</p>

  <p>mouseDrag(Event evt,int x,int y) {</p>

  <p>TextField t =</p>

  <p>(TextField)parent.h.get("mouseDrag");</p>

  <p>t.setText(evt.toString());</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean</p>

  <p>mouseEnter(Event evt,int x,int y) {</p>

  <p>TextField t =</p>

  <p>(TextField)parent.h.get("mouseEnter");</p>

  <p>t.setText(evt.toString());</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean</p>

  <p>mouseExit(Event evt,int x,int y) {</p>

  <p>TextField t =</p>

  <p>(TextField)parent.h.get("mouseExit");</p>

  <p>t.setText(evt.toString());</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean</p>

  <p>mouseMove(Event evt,int x,int y) {</p>

  <p>TextField t =</p>

  <p>(TextField)parent.h.get("mouseMove");</p>

  <p>t.setText(evt.toString());</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean mouseUp(Event evt,int x,int y) {</p>

  <p>TextField t =</p>

  <p>(TextField)parent.h.get("mouseUp");</p>

  <p>t.setText(evt.toString());</p>

  <p>return true;</p>

  <p>}</p>

  <p>}</p>

  <p>public class AutoEvent extends Applet {</p>

  <p>Hashtable h = new Hashtable();</p>

  <p>String[] event = {</p>

  <p>"keyDown", "keyUp", "lostFocus",</p>

  <p>"gotFocus", "mouseDown", "mouseUp",</p>

  <p>"mouseMove", "mouseDrag", "mouseEnter",</p>

  <p>"mouseExit"</p>

  <p>};</p>

  <p>MyButton</p>

  <p>b1 = new MyButton(this, Color.blue, "test1"),</p>

  <p>b2 = new MyButton(this, Color.red, "test2");</p>

  <p>public void init() {</p>

  <p>setLayout(new GridLayout(event.length+1,2));</p>

  <p>for(int i = 0; i &lt; event.length; i++) {</p>

  <p>TextField t = new TextField();</p>

  <p>t.setEditable(false);</p>

  <p>add(new Label(event[i], Label.CENTER));</p>

  <p>add(t);</p>

  <p>h.put(event[i], t);</p>

  <p>}</p>

  <p>add(b1);</p>

  <p>add(b2);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>我们可以看到构建器使用利用自变量同名的方法，所以自变量被赋值，并且使用this来区分：</p>

  <p>this.label = label;</p>

  <p>paint()方法由简单的开始：它用按钮的颜色填充了一个“圆角矩形”，然后画了一个黑线围绕它。请注意size()的使用决定了组件的宽度和长度（当然，是像素）。这之后，paint()看起来非常的复杂，因为有大量的预测去计算出怎样利用“font metrics”集中按钮的标签到按钮里。我们能得到一个相当好的关于继续关注方法调用的主意，它将程序中那些相当平凡的代码挑出，当我们想集中一个标签到一些组件里时，我们正好可以对它进行剪切和粘贴。</p>

  <p>您直到注意到AutoEvent类才能正确地理解keyDown(),keyUp()及其它方法的运行。这包含一个Hashtable（译者注：散列表）去控制字符串来描述关于事件处理的事件和TextField类型。当然，这些能被静态的创建而不是放入Hashtable但我认为您会同意它是更容易使用和改变的。特别是，如果我们需要在AutoEvent中增加或删除一个新的事件类型，我们只需要简单地在事件列队中增加或删除一个字符串――所有的工作都自动地完成了。</p>

  <p>我们查出在keyDown()，keyup()及其它方法中的字符串的位置回到myButton中。这些方法中的任何一个都用父句柄试图回到父窗口。父类是一个AutoEvent，它包含Hashtable h和get()方法，当拥有特定的字符串时，将对一个我们知道的TextField对象产生一个句柄（因此它被选派到那）。然后事件对象修改显示在TextField中的字符串陈述。从我们可以真正注意到举出的例子在我们的程序中运行事件时以来，可以发现这个例子运行起来颇为有趣的。</p>

  <p>13.14 程序片的局限</p>

  <p>出于安全缘故，程序片十分受到限制，并且有很多的事我们都不能做。您一般会问：程序片看起来能做什么，传闻它又能做什么：扩展浏览器中WEB页的功能。自从作为一个网上冲浪者，我们从未真正想了解是否一个WEB页来自友好的或者不友好的站点，我们想要一些可以安全地行动的代码。所以我们可能会注意到大量的限制：</p>

  <p>(1) 一个程序片不能接触到本地的磁盘。这意味着不能在本地磁盘上写和读，我们不想一个程序片通过WEB页面阅读和传送重要的信息。写是被禁止的，当然，因为那将会引起病毒的侵入。当数字签名生效时，这些限制会被解除。</p>

  <p>(2) 程序片不能拥有菜单。（注意：这是规定在Swing中的）这可能会减少关于安全和关于程序简化的麻烦。我们可能会接到有关程序片协调利益以作为WEB页面的一部分的通知；而我们通常不去注意程序片的范围。这儿没有帧和标题条从菜单处弹出，出现的帧和标题条是属于WEB浏览器的。也许将来设计能被改变成允许我们将浏览器菜单和程序片菜单相结合起来――程序片可以影响它的环境将导致太危及整个系统的安全并使程序片过于的复杂。</p>

  <p>(3) 对话框是不被信任的。在Java中，对话框存在一些令人难解的地方。首先，它们不能正确地拒绝程序片，这实在是令人沮丧。如果我们从程序片弹出一个对话框，我们会在对话框上看到一个附上的消息框“不被信任的程序片”。这是因为在理论上，它有可能欺骗用户去考虑他们在通过WEB同一个老顾客的本地应用程序交易并且让他们输入他们的信用卡号。在看到AWT开发的那种GUI后，我们可能会难过地相信任何人都会被那种方法所愚弄。但程序片是一直附着在一个Web页面上的，并可以在浏览器中看到，而对话框没有这种依附关系，所以理论上是可能的。因此，我们很少会见到一个使用对话框的程序片。</p>

  <p>在较新的浏览器中，对受到信任的程序片来说，许多限制都被放宽了（受信任程序片由一个信任源认证）。</p>

  <p>涉及程序片的开发时，还有另一些问题需要考虑：</p>

  <p>■程序片不停地从一个适合不同类的单独的服务器上下载。我们的浏览器能够缓存程序片，但这没有保证。在Java 1.1版中的一个改进是JAR（Java ARchive）文件，它允许将所有的程序片组件（包括其它的类文件、图像、声音）一起打包到一个的能被单个服务器处理下载的压缩文件。“数字签字”（能校验类创建器）可有效地加入每个单独的JAR文件。</p>

  <p>■因为安全方面的缘故，我们做某些工作更加困难，例如访问数据库和发送电子邮件。另外，安全限制规则使访问多个主机变得非常的困难，因为每一件事都必须通过WEB服务器路由，形成一个性能瓶颈，并且单一环节的出错都会导致整个处理的停止。</p>

  <p>■浏览器里的程序片不会拥有同样的本地应用程序运行的控件类型。例如，自从用户可以开关页面以来，在程序片中不会拥有一个形式上的对话框。当用户对一个WEB页面进行改变或退出浏览器时，对我们的程序片而言简直是一场灾难――这时没有办法保存状态，所以如果我们在处理和操作中时，信息会被丢失。另外，当我们离开一个WEB页面时，不同的浏览器会对我们的程序片做不同的操作，因此结果本来就是不确定的。</p>

  <p>13.14.1 程序片的优点</p>

  <p>如果能容忍那些限制，那么程序片的一些优点也是非常突出的，尤其是在我们构建客户／服务器应用或者其它网络应用时：</p>

  <p>■没有安装方面的争议。程序片拥有真正的平台独立性（包括容易地播放声音文件等能力）所以我们不需要针对不同的平台修改代码也不需要任何人根据安装运行任何的“tweaking”。事实上，安装每次自动地将WEB页连同程序片一起，因此安静、自动地更新。在传统的客户机/服务器系统中，建立和安装一个新版本的客户端软件简直就是一场恶梦。</p>

  <p>■因为安全的原因创建在核心Java语言和程序片结构中，我们不必担心坏的代码而导致毁坏某人的系统。这样，连同前面的优点，可使用Java（可从JavaScript和VBScript中选择客户端的WEB编程工具）为所谓的Intrant（在公司内部使用而不向Internet转移的企业内部网络）客户机/服务器开发应用程序。</p>

  <p>■由于程序片是自动同HTML集成的，所以我们有一个内建的独立平台文件系统去支持程序片。这是一个很有趣的方法，因为我们惯于拥有程序文件的一部分而不是相反的拥有文件系统。</p>

  <p>13.15 视窗化应用</p>

  <p>出于安全的缘故，我们会看到在程序片我们的行为非常的受到限制。我们真实地感到，程序片是被临时地加入在WEB浏览器中的，因此，它的功能连同它的相关知识，控件都必须加以限制。但是，我们希望Java能制造一个开窗口的程序去运行一些事物，否则宁愿安放在一个WEB页面上，并且也许我们希望它可以运行一些可靠的应用程序，以及夸张的实时便携性。在这本书前面的章节中我们制造了一些命令行应用程序，但在一些操作环境中（例如：Macintosh）没有命令行。所以我们有很多的理由去利用Java创建一个设置窗口，非程序片的程序。这当然是一个十分合理的要求。</p>

  <p>一个Java设置窗口应用程序可以拥有菜单和对话框（这对一个程序片来说是不可能的和很困难的），可是如果我们使用一个老版本的Java，我们将会牺牲本地操作系统环境的外观和感受。JFC/Swing库允许我们制造一个保持原来操作系统环境的外观和感受的应用程序。如果我们想建立一个设置窗口应用程序，它会合理地运作，同样，如果我们可以使用最新版本的Java并且集合所有的工具，我们就可以发布不会使用户困惑的应用程序。如果因为一些原因，我们被迫使用老版本的Java，请在毁坏以建立重要的设置窗口的应用程序前仔细地考虑。</p>

  <p>13.15.1 菜单</p>

  <p>直接在程序片中安放一个菜单是不可能的（Java 1.0,Java1.1和Swing库不允许），因为它们是针对应用程序的。继续，如果您不相信我并且确定在程序片中可以合理地拥有菜单，那么您可以去试验一下。程序片中没有setMenuBar()方法，而这种方法是附在菜单中的（我们会看到它可以合理地在程序片产生一个帧，并且帧包含菜单）。</p>

  <p>有四种不同类型的MenuComponent（菜单组件），所有的菜单组件起源于抽象类：菜单条（我们可以在一个事件帧里拥有一个菜单条），菜单去支配一个单独的下拉菜单或者子菜单、菜单项来说明菜单里一个单个的元素，以及起源于MenuItem,产生检查标志（checkmark）去显示菜单项是否被选择的CheckBoxMenuItem。</p>

  <p>不同的系统使用不同的资源，对Java和AWT而言，我们必须在源代码中手工汇编所有的菜单。</p>

  <p>//: Menu1.java</p>

  <p>// Menus work only with Frames.</p>

  <p>// Shows submenus, checkbox menu items</p>

  <p>// and swapping menus.</p>

  <p>import java.awt.*;</p>

  <p>public class Menu1 extends Frame {</p>

  <p>String[] flavors = { "Chocolate", "Strawberry",</p>

  <p>"Vanilla Fudge Swirl", "Mint Chip",</p>

  <p>"Mocha Almond Fudge", "Rum Raisin",</p>

  <p>"Praline Cream", "Mud Pie" };</p>

  <p>TextField t = new TextField("No flavor", 30);</p>

  <p>MenuBar mb1 = new MenuBar();</p>

  <p>Menu f = new Menu("File");</p>

  <p>Menu m = new Menu("Flavors");</p>

  <p>Menu s = new Menu("Safety");</p>

  <p>// Alternative approach:</p>

  <p>CheckboxMenuItem[] safety = {</p>

  <p>new CheckboxMenuItem("Guard"),</p>

  <p>new CheckboxMenuItem("Hide")</p>

  <p>};</p>

  <p>MenuItem[] file = {</p>

  <p>new MenuItem("Open"),</p>

  <p>new MenuItem("Exit")</p>

  <p>};</p>

  <p>// A second menu bar to swap to:</p>

  <p>MenuBar mb2 = new MenuBar();</p>

  <p>Menu fooBar = new Menu("fooBar");</p>

  <p>MenuItem[] other = {</p>

  <p>new MenuItem("Foo"),</p>

  <p>new MenuItem("Bar"),</p>

  <p>new MenuItem("Baz"),</p>

  <p>};</p>

  <p>Button b = new Button("Swap Menus");</p>

  <p>public Menu1() {</p>

  <p>for(int i = 0; i &lt; flavors.length; i++) {</p>

  <p>m.add(new MenuItem(flavors[i]));</p>

  <p>// Add separators at intervals:</p>

  <p>if((i+1) % 3 == 0)</p>

  <p>m.addSeparator();</p>

  <p>}</p>

  <p>for(int i = 0; i &lt; safety.length; i++)</p>

  <p>s.add(safety[i]);</p>

  <p>f.add(s);</p>

  <p>for(int i = 0; i &lt; file.length; i++)</p>

  <p>f.add(file[i]);</p>

  <p>mb1.add(f);</p>

  <p>mb1.add(m);</p>

  <p>setMenuBar(mb1);</p>

  <p>t.setEditable(false);</p>

  <p>add("Center", t);</p>

  <p>// Set up the system for swapping menus:</p>

  <p>add("North", b);</p>

  <p>for(int i = 0; i &lt; other.length; i++)</p>

  <p>fooBar.add(other[i]);</p>

  <p>mb2.add(fooBar);</p>

  <p>}</p>

  <p>public boolean handleEvent(Event evt) {</p>

  <p>if(evt.id == Event.WINDOW_DESTROY)</p>

  <p>System.exit(0);</p>

  <p>else</p>

  <p>return super.handleEvent(evt);</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean action(Event evt, Object arg) {</p>

  <p>if(evt.target.equals(b)) {</p>

  <p>MenuBar m = getMenuBar();</p>

  <p>if(m == mb1) setMenuBar(mb2);</p>

  <p>else if (m == mb2) setMenuBar(mb1);</p>

  <p>}</p>

  <p>else if(evt.target instanceof MenuItem) {</p>

  <p>if(arg.equals("Open")) {</p>

  <p>String s = t.getText();</p>

  <p>boolean chosen = false;</p>

  <p>for(int i = 0; i &lt; flavors.length; i++)</p>

  <p>if(s.equals(flavors[i])) chosen = true;</p>

  <p>if(!chosen)</p>

  <p>t.setText("Choose a flavor first!");</p>

  <p>else</p>

  <p>t.setText("Opening "+ s +". Mmm, mm!");</p>

  <p>}</p>

  <p>else if(evt.target.equals(file[1]))</p>

  <p>System.exit(0);</p>

  <p>// CheckboxMenuItems cannot use String</p>

  <p>// matching; you must match the target:</p>

  <p>else if(evt.target.equals(safety[0]))</p>

  <p>t.setText("Guard the Ice Cream! " +</p>

  <p>"Guarding is " + safety[0].getState());</p>

  <p>else if(evt.target.equals(safety[1]))</p>

  <p>t.setText("Hide the Ice Cream! " +</p>

  <p>"Is it cold? " + safety[1].getState());</p>

  <p>else</p>

  <p>t.setText(arg.toString());</p>

  <p>}</p>

  <p>else</p>

  <p>return super.action(evt, arg);</p>

  <p>return true;</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Menu1 f = new Menu1();</p>

  <p>f.resize(300,200);</p>

  <p>f.show();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在这个程序中，我避免了为每个菜单编写典型的冗长的add()列表调用，因为那看起来像许多的无用的标志。取而代之的是，我安放菜单项到数组中，然后在一个for的循环中通过每个数组调用add()简单地跳过。这样的话，增加和减少菜单项变得没那么讨厌了。</p>

  <p>作为一个可选择的方法（我发现这很难令我满意，因为它需要更多的分配）CheckboxMenuItems在数组的句柄中被创建是被称为安全创建；这对数组文件和其它的文件而言是真正的安全。</p>

  <p>程序中创建了不是一个而是二个的菜单条来证明菜单条在程序运行时能被交换激活。我们可以看到菜单条怎样组成菜单，每个菜单怎样组成菜单项（MenuItems），chenkboxMenuItems或者其它的菜单（产生子菜单）。当菜单组合后，可以用setMenuBar()方法安装到现在的程序中。值得注意的是当按钮被压下时，它将检查当前的菜单安装使用getMenuBar()，然后安放其它的菜单条在它的位置上。</p>

  <p>当测试是“open”（即开始）时，注意拼写和大写，如果开始时没有对象，Java发出no error（没有错误）的信号。这种字符串比较是一个明显的程序设计错误源。</p>

  <p>校验和非校验的菜单项自动地运行，与之相关的CheckBoxMenuItems着实令人吃惊，这是因为一些原因它们不允许字符串匹配。（这似乎是自相矛盾的，尽管字符串匹配并不是一种很好的办法。）因此，我们可以匹配一个目标对象而不是它们的标签。当演示时，getState()方法用来显示状态。我们同样可以用setState()改变CheckboxMenuItem的状态。</p>

  <p>我们可能会认为一个菜单可以合理地置入超过一个的菜单条中。这看似合理，因为所有我们忽略的菜单条的add()方法都是一个句柄。然而，如果我们试图这样做，这个结果将会变得非常的别扭，而远非我们所希望得到的结果。（很难知道这是一个编程中的错误或者说是他们试图使它以这种方法去运行所产生的。）这个例子同样向我们展示了为什么我们需要建立一个应用程序以替代程序片。（这是因为应用程序能支持菜单，而程序片是不能直接使用菜单的。）我们从帧处继承代替从程序片处继承。另外，我们为类建一个构建器以取代init()安装事件。最后，我们创建一个main()方法并且在我们建的新型对象里，调整它的大小，然后调用show()。它与程序片只在很小的地方有不同之处，然而这时它已经是一个独立的设置窗口应用程序并且我们可以使用菜单。</p>

  <p>13.15.2 对话框</p>

  <p>对话框是一个从其它窗口弹出的窗口。它的目的是处理一些特殊的争议和它们的细节而不使原来的窗口陷入混乱之中。对话框大量在设置窗口的编程环境中使用，但就像前面提到的一样，鲜于在程序片中使用。</p>

  <p>我们需要从对话类处继承以创建其它类型的窗口、像帧一样的对话框。和窗框不同，对话框不能拥有菜单条也不能改变光标，但除此之外它们十分的相似。一个对话框拥有布局管理器（默认的是BorderLayout布局管理器）和过载action()等等，或用handleEvent()去处理事件。我们会注意到handleEvent()的一个重要差异：当WINDOW_DESTORY事件发生时，我们并不希望关闭正在运行的应用程序！</p>

  <p>相反，我们可以使用对话窗口通过调用dispace()释放资源。在下面的例子中，对话框是由定义在那儿作为类的ToeButton的特殊按钮组成的网格构成的（利用GridLayout布局管理器）。ToeButton按钮围绕它自已画了一个帧，并且依赖它的状态：在空的中的“Ｘ”或者“Ｏ”。它从空白开始，然后依靠使用者的选择，转换成“Ｘ”或“Ｏ”。但是，当我们单击在按钮上时，它会在“Ｘ”和“Ｏ”之间来回交换。（这产生了一种类似填字游戏的感觉，当然比它更令人讨厌。）另外，这个对话框可以被设置为在主应用程序窗口中为很多的行和列变更号码。</p>

  <p>//: ToeTest.java</p>

  <p>// Demonstration of dialog boxes</p>

  <p>// and creating your own components</p>

  <p>import java.awt.*;</p>

  <p>class ToeButton extends Canvas {</p>

  <p>int state = ToeDialog.BLANK;</p>

  <p>ToeDialog parent;</p>

  <p>ToeButton(ToeDialog parent) {</p>

  <p>this.parent = parent;</p>

  <p>}</p>

  <p>public void paint(Graphics g) {</p>

  <p>int x1 = 0;</p>

  <p>int y1 = 0;</p>

  <p>int x2 = size().width - 1;</p>

  <p>int y2 = size().height - 1;</p>

  <p>g.drawRect(x1, y1, x2, y2);</p>

  <p>x1 = x2/4;</p>

  <p>y1 = y2/4;</p>

  <p>int wide = x2/2;</p>

  <p>int high = y2/2;</p>

  <p>if(state == ToeDialog.XX) {</p>

  <p>g.drawLine(x1, y1, x1 + wide, y1 + high);</p>

  <p>g.drawLine(x1, y1 + high, x1 + wide, y1);</p>

  <p>}</p>

  <p>if(state == ToeDialog.OO) {</p>

  <p>g.drawOval(x1, y1, x1+wide/2, y1+high/2);</p>

  <p>}</p>

  <p>}</p>

  <p>public boolean</p>

  <p>mouseDown(Event evt, int x, int y) {</p>

  <p>if(state == ToeDialog.BLANK) {</p>

  <p>state = parent.turn;</p>

  <p>parent.turn= (parent.turn == ToeDialog.XX ?</p>

  <p>ToeDialog.OO : ToeDialog.XX);</p>

  <p>}</p>

  <p>else</p>

  <p>state = (state == ToeDialog.XX ?</p>

  <p>ToeDialog.OO : ToeDialog.XX);</p>

  <p>repaint();</p>

  <p>return true;</p>

  <p>}</p>

  <p>}</p>

  <p>class ToeDialog extends Dialog {</p>

  <p>// w = number of cells wide</p>

  <p>// h = number of cells high</p>

  <p>static final int BLANK = 0;</p>

  <p>static final int XX = 1;</p>

  <p>static final int OO = 2;</p>

  <p>int turn = XX; // Start with x's turn</p>

  <p>public ToeDialog(Frame parent, int w, int h) {</p>

  <p>super(parent, "The game itself", false);</p>

  <p>setLayout(new GridLayout(w, h));</p>

  <p>for(int i = 0; i &lt; w * h; i++)</p>

  <p>add(new ToeButton(this));</p>

  <p>resize(w * 50, h * 50);</p>

  <p>}</p>

  <p>public boolean handleEvent(Event evt) {</p>

  <p>if(evt.id == Event.WINDOW_DESTROY)</p>

  <p>dispose();</p>

  <p>else</p>

  <p>return super.handleEvent(evt);</p>

  <p>return true;</p>

  <p>}</p>

  <p>}</p>

  <p>public class ToeTest extends Frame {</p>

  <p>TextField rows = new TextField("3");</p>

  <p>TextField cols = new TextField("3");</p>

  <p>public ToeTest() {</p>

  <p>setTitle("Toe Test");</p>

  <p>Panel p = new Panel();</p>

  <p>p.setLayout(new GridLayout(2,2));</p>

  <p>p.add(new Label("Rows", Label.CENTER));</p>

  <p>p.add(rows);</p>

  <p>p.add(new Label("Columns", Label.CENTER));</p>

  <p>p.add(cols);</p>

  <p>add("North", p);</p>

  <p>add("South", new Button("go"));</p>

  <p>}</p>

  <p>public boolean handleEvent(Event evt) {</p>

  <p>if(evt.id == Event.WINDOW_DESTROY)</p>

  <p>System.exit(0);</p>

  <p>else</p>

  <p>return super.handleEvent(evt);</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean action(Event evt, Object arg) {</p>

  <p>if(arg.equals("go")) {</p>

  <p>Dialog d = new ToeDialog(</p>

  <p>this,</p>

  <p>Integer.parseInt(rows.getText()),</p>

  <p>Integer.parseInt(cols.getText()));</p>

  <p>d.show();</p>

  <p>}</p>

  <p>else</p>

  <p>return super.action(evt, arg);</p>

  <p>return true;</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Frame f = new ToeTest();</p>

  <p>f.resize(200,100);</p>

  <p>f.show();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>ToeButton类保留了一个句柄到它ToeDialog型的父类中。正如前面所述，ToeButton和ToeDialog高度的结合因为一个ToeButton只能被一个ToeDialog所使用，但它却解决了一系列的问题，事实上这实在不是一个糟糕的解决方案因为没有另外的可以记录用户选择的对话类。当然我们可以使用其它的制造ToeDialog.turn（ToeButton的静态的一部分）方法。这种方法消除了它们的紧密联系，但却阻止了我们一次拥有多个ToeDialog（无论如何，至少有一个正常地运行）。</p>

  <p>paint()是一种与图形有关的方法：它围绕按钮画出矩形并画出“Ｘ”或“Ｏ”。这完全是冗长的计算，但却十分的直观。</p>

  <p>一个鼠标单击被过载的mouseDown()方法所俘获，最要紧的是检查是否有事件写在按钮上。如果没有，父窗口会被询问以找出谁选择了它并用来确定按钮的状态。值得注意的是按钮随后交回到父类中并且改变它的选择。如果按钮已经显示这为“Ｘ”和“Ｏ”，那么它们会被改变状态。我们能注意到本书第三章中描述的在这些计算中方便的使用的三个一组的If-else。当一个按钮的状态改变后，按钮会被重画。</p>

  <p>ToeDialog的构建器十分的简单：它像我们所需要的一样增加一些按钮到GridLayout布局管理器中，然后调整每个按钮每边大小为50个像素（如果我们不调整窗口，那么它就不会显示出来）。注意handleEvent()正好为WINDOW_DESTROY调用dispose()，因此整个应用程序不会被关闭。</p>

  <p>ToeTest设置整个应用程序以创建TextField（为输入按钮网格的行和列）和“go”按钮。我们会领会action()在这个程序中使用不太令人满意的“字符串匹配”技术来测试按钮的按下（请确定我们拼写和大写都是正确的！）。当按钮按下时，TextField中的数据将被取出，并且，因为它们在字符串结构中，所以需要利用静态的Integer.paresInt()方法来转变成中断。一旦对话类被建立，我们就必须调用show()方法来显示和激活它。</p>

  <p>我们会注意到ToeDialog对象赋值给一个对话句柄 d。这是一个上溯造型的例子，尽管它没有真正地产生重要的差异，因为所有的事件都是show()调用的。但是，如果我们想调用ToeDialog中已经存在的一些方法，我们需要对ToeDialog句柄赋值，就不会在一个上溯中丢失信息。</p>

  <p>1. 文件对话类</p>

  <p>在一些操作系统中拥有许多的特殊内建对话框去处理选择的事件，例如：字库，颜色，打印机以及类似的事件。几乎所有的操作系统都支持打开和保存文件，但是，Java的FileDialog包更容易使用。当然这会不再检测所有使用的程序片，因为程序片在本地磁盘上既不能读也不能写文件。（这会在新的浏览器中交换程序片的信任关系。）</p>

  <p>下面的应用程序运用了两个文件对话类的窗体，一个是打开，一个是保存。大多数的代码到如今已为我们所熟悉，而所有这些有趣的活动发生在两个不同按钮单击事件的action()方法中。</p>

  <p>//: FileDialogTest.java</p>

  <p>// Demonstration of File dialog boxes</p>

  <p>import java.awt.*;</p>

  <p>public class FileDialogTest extends Frame {</p>

  <p>TextField filename = new TextField();</p>

  <p>TextField directory = new TextField();</p>

  <p>Button open = new Button("Open");</p>

  <p>Button save = new Button("Save");</p>

  <p>public FileDialogTest() {</p>

  <p>setTitle("File Dialog Test");</p>

  <p>Panel p = new Panel();</p>

  <p>p.setLayout(new FlowLayout());</p>

  <p>p.add(open);</p>

  <p>p.add(save);</p>

  <p>add("South", p);</p>

  <p>directory.setEditable(false);</p>

  <p>filename.setEditable(false);</p>

  <p>p = new Panel();</p>

  <p>p.setLayout(new GridLayout(2,1));</p>

  <p>p.add(filename);</p>

  <p>p.add(directory);</p>

  <p>add("North", p);</p>

  <p>}</p>

  <p>public boolean handleEvent(Event evt) {</p>

  <p>if(evt.id == Event.WINDOW_DESTROY)</p>

  <p>System.exit(0);</p>

  <p>else</p>

  <p>return super.handleEvent(evt);</p>

  <p>return true;</p>

  <p>}</p>

  <p>public boolean action(Event evt, Object arg) {</p>

  <p>if(evt.target.equals(open)) {</p>

  <p>// Two arguments, defaults to open file:</p>

  <p>FileDialog d = new FileDialog(this,</p>

  <p>"What file do you want to open?");</p>

  <p>d.setFile("*.java"); // Filename filter</p>

  <p>d.setDirectory("."); // Current directory</p>

  <p>d.show();</p>

  <p>String openFile;</p>

  <p>if((openFile = d.getFile()) != null) {</p>

  <p>filename.setText(openFile);</p>

  <p>directory.setText(d.getDirectory());</p>

  <p>} else {</p>

  <p>filename.setText("You pressed cancel");</p>

  <p>directory.setText("");</p>

  <p>}</p>

  <p>}</p>

  <p>else if(evt.target.equals(save)) {</p>

  <p>FileDialog d = new FileDialog(this,</p>

  <p>"What file do you want to save?",</p>

  <p>FileDialog.SAVE);</p>

  <p>d.setFile("*.java");</p>

  <p>d.setDirectory(".");</p>

  <p>d.show();</p>

  <p>String saveFile;</p>

  <p>if((saveFile = d.getFile()) != null) {</p>

  <p>filename.setText(saveFile);</p>

  <p>directory.setText(d.getDirectory());</p>

  <p>} else {</p>

  <p>filename.setText("You pressed cancel");</p>

  <p>directory.setText("");</p>

  <p>}</p>

  <p>}</p>

  <p>else</p>

  <p>return super.action(evt, arg);</p>

  <p>return true;</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Frame f = new FileDialogTest();</p>

  <p>f.resize(250,110);</p>

  <p>f.show();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>对一个“打开文件”对话框，我们使用构建器设置两个自变量；首先是父窗口句柄，其次是FileDialog标题条的标题。setFile()方法提供一个初始文件名－－也许本地操作系统支持通配符，因此在这个例子中所有的.java文件最开头会被显示出来。setDirectory()方法选择文件决定开始的目录（一般而言，操作系统允许用户改变目录）。</p>

  <p>show()命令直到对话类关闭才返回。FileDialog对象一直存在，因此我们可以从它那里读取数据。如果我们调用getFile()并且它返回空，这意味着用户退出了对话类。文件名和调用getDirectory()方法的结果都显示在TextFields里。</p>

  <p>按钮的保存工作使用同样的方法，除了因为FileDialog而使用不同的构建器。这个构建器设置了三个自变量并且第三的一个自变量必须为FileDialog.SAVE或FileDialog.OPEN。</p>

  <p>13.16 新型AWT</p>

  <p>在Java 1.1中一个显著的改变就是完善了新AWT的创新。大多数的改变围绕在Java 1.1中使用的新事件模型：老的事件模型是糟糕的、笨拙的、非面向对象的，而新的事件模型可能是我所见过的最优秀的。难以理解一个如此糟糕的（老的AWT）和一个如此优秀的（新的事件模型）程序语言居然出自同一个集团之手。新的考虑事件的方法看来中止了，因此争议不再变成障碍，从而轻易进入我们的意识里；相反，它是一个帮助我们设计系统的工具。它同样是Java Beans的精华，我们会在本章后面部分进入讲述。</p>

  <p>新的方法设计对象做为“事件源”和“事件接收器”以代替老AWT的非面向对象串联的条件语句。正象我们将看到的内部类的用途是集成面向对象的原始状态的新事件。另外，事件现在被描绘为在一个类体系以取代单一的类并且我们可以创建自己的事件类型。</p>

  <p>我们同样会发现，如果我们采用老的AWT编程，Java 1.1版会产生一些看起来不合理的名字转换。例如，setsize()改成resize()。当我们学习Java Beans时这会变得更加的合理，因为Beans使用一个独特的命名协议。名字必须被修改以在Beans中产生新的标准AWT组件。</p>

  <p>剪贴板操作在Java 1.1版中也得到支持，尽管拖放操作“将在新版本中被支持”。我们可能访问桌面色彩组织，所以我们的Java可以同其余桌面保持一致。可以利用弹出式菜单，并且为图像和图形作了改进。也同样支持鼠标操作。还有简单的为打印的API以及简单地支持滚动。</p>

  <p>13.16.1 新的事件模型</p>

  <p>在新的事件模型的组件可以开始一个事件。每种类型的事件被一个个别的类所描绘。当事件开始后，它受理一个或更多事件指明“接收器”。因此，事件源和处理事件的地址可以被分离。</p>

  <p>每个事件接收器都是执行特定的接收器类型接口的类对象。因此作为一个程序开发者，我们所要做的是创建接收器对象并且在被激活事件的组件中进行注册。event-firing组件调用一个addXXXListener()方法来完成注册，以描述XXX事件类型接受。我们可以容易地了解到以addListened名的方法通知我们任何的事件类型都可以被处理，如果我们试图接收事件我们会发现编译时我们的错误。Java Beans同样使用这种addListener名的方法去判断那一个程序可以运行。</p>

  <p>我们所有的事件逻辑将装入到一个接收器类中。当我们创建一个接收器类时唯一的一点限制是必须执行专用的接口。我们可以创建一个全局接收器类，这种情况在内部类中有助于被很好地使用，不仅仅是因为它们提供了一个理论上的接收器类组到它们服务的UI或业务逻辑类中，但因为（正像我们将会在本章后面看到的）事实是一个内部类维持一个句柄到它的父对象，提供了一个很好的通过类和子系统边界的调用方法。</p>

  <p>一个简单的例子将使这一切变得清晰明确。同时思考本章前部Button2.java例子与这个例子的差异。</p>

  <p>//: Button2New.java</p>

  <p>// Capturing button presses</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*; // Must add this</p>

  <p>import java.applet.*;</p>

  <p>public class Button2New extends Applet {</p>

  <p>Button</p>

  <p>b1 = new Button("Button 1"),</p>

  <p>b2 = new Button("Button 2");</p>

  <p>public void init() {</p>

  <p>b1.addActionListener(new B1());</p>

  <p>b2.addActionListener(new B2());</p>

  <p>add(b1);</p>

  <p>add(b2);</p>

  <p>}</p>

  <p>class B1 implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>getAppletContext().showStatus("Button 1");</p>

  <p>}</p>

  <p>}</p>

  <p>class B2 implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>getAppletContext().showStatus("Button 2");</p>

  <p>}</p>

  <p>}</p>

  <p>/* The old way:</p>

  <p>public boolean action(Event evt, Object arg) {</p>

  <p>if(evt.target.equals(b1))</p>

  <p>getAppletContext().showStatus("Button 1");</p>

  <p>else if(evt.target.equals(b2))</p>

  <p>getAppletContext().showStatus("Button 2");</p>

  <p>// Let the base class handle it:</p>

  <p>else</p>

  <p>return super.action(evt, arg);</p>

  <p>return true; // We've handled it here</p>

  <p>}</p>

  <p>*/</p>

  <p>} ///:~</p>

  <p>我们可比较两种方法，老的代码在左面作为注解。在init()方法里，只有一个改变就是增加了下面的两行：</p>

  <p>b1.addActionListener(new B1());</p>

  <p>b2.addActionListener(new B2());</p>

  <p>按钮按下时，addActionListener()通知按钮对象被激活。B1和B2类都是执行接口ActionListener的内部类。这个接口包括一个单一的方法actionPerformed()（这意味着当事件激活时，这个动作将被执行）。注意actionPreformed()方法不是一个普通事件，说得更恰当些是一个特殊类型的事件，ActionEvent。如果我们想提取特殊ActionEvent的信息，因此我们不需要故意去测试和下溯造型自变量。</p>

  <p>对编程者来说一个最好的事便是actionPerformed()十分的简单易用。它是一个可以调用的方法。同老的action()方法比较，老的方法我们必须指出发生了什么和适当的动作，同样，我们会担心调用基础类action()的版本并且返回一个值去指明是否被处理。在新的事件模型中，我们知道所有事件测试推理自动进行，因此我们不必指出发生了什么；我们刚刚表示发生了什么，它就自动地完成了。如果我们还没有提出用新的方法覆盖老的方法，我们会很快提出。</p>

  <p>13.16.2 事件和接收者类型</p>

  <p>所有AWT组件都被改变成包含addXXXListener()和removeXXXListener()方法，因此特定的接收器类型可从每个组件中增加和删除。我们会注意到“XXX”在每个场合中同样表示自变量的方法，例如，addFooListener(FooListener fl)。下面这张表格总结了通过提供addXXXListener()和removeXXXListener()方法，从而支持那些特定事件的相关事件、接收器、方法以及组件。</p>

  <p>事件，接收器接口及添加和删除方法 支持这个事件的组件</p>

  <p>Event, listener interface and add- and remove-methods</p>

  <p>Components supporting this event</p>

  <p>ActionEvent</p>

  <p>ActionListener</p>

  <p>addActionListener( )</p>

  <p>removeActionListener( )</p>

  <p>Button, List, TextField, MenuItem, and its derivatives including CheckboxMenuItem, Menu, and PopupMenu</p>

  <p>AdjustmentEvent</p>

  <p>AdjustmentListener</p>

  <p>addAdjustmentListener( )</p>

  <p>removeAdjustmentListener( )</p>

  <p>Scrollbar</p>

  <p>Anything you create that implements the Adjustable interface</p>

  <p>ComponentEvent</p>

  <p>ComponentListener</p>

  <p>addComponentListener( )</p>

  <p>removeComponentListener( )</p>

  <p>Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField</p>

  <p>ContainerEvent</p>

  <p>ContainerListener</p>

  <p>addContainerListener( )</p>

  <p>removeContainerListener( )</p>

  <p>Container and its derivatives, including Panel, Applet, ScrollPane, Window, Dialog, FileDialog, and Frame</p>

  <p>FocusEvent</p>

  <p>FocusListener</p>

  <p>addFocusListener( )</p>

  <p>removeFocusListener( )</p>

  <p>Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame Label, List, Scrollbar, TextArea, and TextField</p>

  <p>KeyEvent</p>

  <p>KeyListener</p>

  <p>addKeyListener( )</p>

  <p>removeKeyListener( )</p>

  <p>Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField</p>

  <p>MouseEvent (for both clicks and motion)</p>

  <p>MouseListener</p>

  <p>addMouseListener( )</p>

  <p>removeMouseListener( )</p>

  <p>Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField</p>

  <p>MouseEvent[55] (for both clicks and motion)</p>

  <p>MouseMotionListener</p>

  <p>addMouseMotionListener( )</p>

  <p>removeMouseMotionListener( )</p>

  <p>Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField</p>

  <p>WindowEvent</p>

  <p>WindowListener</p>

  <p>addWindowListener( )</p>

  <p>removeWindowListener( )</p>

  <p>Window and its derivatives, including Dialog, FileDialog, and Frame</p>

  <p>ItemEvent</p>

  <p>ItemListener</p>

  <p>addItemListener( )</p>

  <p>removeItemListener( )</p>

  <p>Checkbox, CheckboxMenuItem, Choice, List, and anything that implements the ItemSelectable interface</p>

  <p>TextEvent</p>

  <p>TextListener</p>

  <p>addTextListener( )</p>

  <p>removeTextListener( )</p>

  <p>Anything derived from TextComponent, including TextArea and TextField⑤：即使表面上如此，但实际上并没有MouseMotiionEvent（鼠标运动事件）。单击和运动都合成到MouseEvent里，所以MouseEvent在表格中的这种另类行为并非一个错误。</p>

  <p>可以看到，每种类型的组件只为特定类型的事件提供了支持。这有助于我们发现由每种组件支持的事件，如下表所示：</p>

  <p>组件类型 支持的事件</p>

  <p>Component type</p>

  <p>Events supported by this component</p>

  <p>Adjustable</p>

  <p>AdjustmentEvent</p>

  <p>Applet</p>

  <p>ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent</p>

  <p>Button</p>

  <p>ActionEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent</p>

  <p>Canvas</p>

  <p>FocusEvent, KeyEvent, MouseEvent, ComponentEvent</p>

  <p>Checkbox</p>

  <p>ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent</p>

  <p>CheckboxMenuItem</p>

  <p>ActionEvent, ItemEvent</p>

  <p>Choice</p>

  <p>ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent</p>

  <p>Component</p>

  <p>FocusEvent, KeyEvent, MouseEvent, ComponentEvent</p>

  <p>Container</p>

  <p>ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent</p>

  <p>Dialog</p>

  <p>ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent</p>

  <p>FileDialog</p>

  <p>ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent</p>

  <p>Frame</p>

  <p>ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent</p>

  <p>Label</p>

  <p>F</p>

  <div class="mbppagebreak"></div>
</body>
</html>
